﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Asol.Reporting.Support
{
    // Výjimky používám tří typů, podle role toho, kdo je může odstranit: UserException: uživatel; AdminException: správce, DevException: programátor. Podle toho se i řídí okno, které se po chybě zobrazí.
    #region class UserException : jde o chybu zadání dat, kterou má napravit uživatel.
    /// <summary>
    /// UserException : jde o chybu zadání dat, kterou má napravit uživatel.
    /// Chyba by měla obsahovat popis cesty k nápravě.
    /// Je potomkem RootException.
    /// </summary>
    public class UserException : RootException
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        public UserException()
            : base()
        { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="message"></param>
        public UserException(string message)
            : base(message)
        { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="message"></param>
        /// <param name="data"></param>
        public UserException(string message, params object[] data)
            : base(message, data)
        { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="message"></param>
        /// <param name="innerException"></param>
        public UserException(string message, Exception innerException)
            : base(message, innerException)
        { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="message"></param>
        /// <param name="innerException"></param>
        /// <param name="data"></param>
        public UserException(string message, Exception innerException, params object[] data)
            : base(message, innerException, data)
        { }
    }
    #endregion
    #region class AdminException : jde o chybu nastavení systému nebo neexistující soubory, měl by ji napravit správce.
    /// <summary>
    /// UserException : jde o chybu zadání dat, kterou má napravit uživatel.
    /// Chyba by měla obsahovat popis cesty k nápravě.
    /// Je potomkem RootException.
    /// </summary>
    public class AdminException : RootException
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        public AdminException()
            : base()
        { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="message"></param>
        public AdminException(string message)
            : base(message)
        { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="message"></param>
        /// <param name="data"></param>
        public AdminException(string message, params object[] data)
            : base(message, data)
        { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="message"></param>
        /// <param name="innerException"></param>
        public AdminException(string message, Exception innerException)
            : base(message, innerException)
        { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="message"></param>
        /// <param name="innerException"></param>
        /// <param name="data"></param>
        public AdminException(string message, Exception innerException, params object[] data)
            : base(message, innerException, data)
        { }
    }
    #endregion
    #region class DevException : chyba kódu, kterou by měl opravit programátor.
    /// <summary>
    /// DevException : chyba kódu, kterou by měl opravit programátor.
    /// </summary>
    public class DevException : RootException
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        public DevException()
            : base()
        { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="message"></param>
        public DevException(string message)
            : base(message)
        { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="message"></param>
        /// <param name="innerException"></param>
        public DevException(string message, Exception innerException)
            : base(message, innerException)
        { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="message"></param>
        /// <param name="innerException"></param>
        /// <param name="data"></param>
        public DevException(string message, params object[] data)
            : base(Assistant.GetMessage(message, data))
        {
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="message"></param>
        /// <param name="innerException"></param>
        /// <param name="data"></param>
        public DevException(string message, Exception innerException, params object[] data)
            : base(Assistant.GetMessage(message, data), innerException)
        { }
    }
    #endregion
    #region class RootException : výchozí typ výjimek. Tato výjimka je abstract, nelze ji instancovat, ale lze ji podědit.
    /// <summary>
    /// RootException : výchozí typ výjimek. Tato výjimka je abstract, nelze ji instancovat, ale lze ji podědit.
    /// </summary>
    public abstract class RootException : Exception
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        public RootException()
            : base()
        { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="message"></param>
        public RootException(string message)
            : base(message)
        { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="message"></param>
        /// <param name="innerException"></param>
        public RootException(string message, Exception innerException)
            : base(message, innerException)
        { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="message"></param>
        /// <param name="innerException"></param>
        /// <param name="data"></param>
        public RootException(string message, params object[] data)
            : base(Assistant.GetMessage(message, data))
        {
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="message"></param>
        /// <param name="innerException"></param>
        /// <param name="data"></param>
        public RootException(string message, Exception innerException, params object[] data)
            : base(Assistant.GetMessage(message, data), innerException)
        { }
    }
    #endregion
    #region Analýza StackTrace, class StackTraceItem
    /// <summary>
    /// Jedna strukturovaná položka stacku (metoda s chybou, soubor, řádek)
    /// </summary>
    public class StackTraceItem
    {
        #region Property
        private StackTraceItem(string fullMethod, string param, string file, int line)
        {
            this.Namespace = "";
            this.TypeName = "";
            this.MethodName = "";
            if (!String.IsNullOrEmpty(fullMethod))
            {
                string[] parts = fullMethod.Split('.');
                int count = parts.Length;
                if (count > 2)
                {
                    string nmspace = "";
                    for (int i = 0; i < (count - 2); i++)
                        nmspace += (i == 0 ? "" : ".") + parts[i];
                    this.Namespace = nmspace;
                    this.TypeName = parts[count - 2];
                    this.MethodName = parts[count - 1];
                }
            }

            this.Parameters = param;
            this.File = file;
            this.Line = line;
        }
        public string Namespace { get; private set; }
        public string TypeName { get; private set; }
        public string MethodName { get; private set; }
        public string Parameters { get; private set; }
        public string File { get; private set; }
        public int Line { get; private set; }
        public override string ToString()
        {
            return this.TypeName + "." + this.MethodName + this.Parameters;
        }
        #endregion
        #region Analýza
        public static List<StackTraceItem> ParseStack(Exception exc)
        {
            if (exc == null) return null;
            return ParseStack(exc.StackTrace);
        }
        public static List<StackTraceItem> ParseStack(string stack)
        {
            List<StackTraceItem> list = new List<StackTraceItem>();
            if (String.IsNullOrEmpty(stack)) return list;

            Parser.ParserSetting setting = CreateSetting();
            List<Parser.ParserSegment> segments = Parser.Parser.ParseString(stack, setting);

            foreach (Parser.ParserSegment segment in segments)
            {
                int count = segment.ValueCount;
                if (count < 5) continue;
                // Méně než 5 není korektní řádek, protože: 
                // 0=mezera, 1="v", 2=mezera, 3=Type.Method, 4=parameters, 5=mezera, 6="v", 7=mezera, 
                // 8=začátek souboru, ..., (count-1)

                string file = "";
                int line = 0;
                if (count >= 7)
                {   // Pravděpodobně máme zachycen zdrojový soubor + číslo řádku.
                    int i = count - 1;
                    while (i > 8)
                    {
                        string text = segment.ValueList[i].Text;
                        int l;
                        if (!String.IsNullOrEmpty(text) && Int32.TryParse(text, out l))
                        {
                            line = l;
                            break;
                        }
                    }
                    for (int f = 8; f < (i - 3); f++)
                        file += segment.ValueList[f].Text;
                }

                StackTraceItem item = new StackTraceItem(segment.ValueList[3].Text, segment.ValueList[4].Text, file, line);
                list.Add(item);
            }

            return list;
        }

        private static Parser.ParserSegmentValue SearchFor(Parser.ParserSegment segment, ref int i, Func<Parser.ParserSegmentValue, bool> predicate)
        {
            while (i < segment.ValueCount)
            {
                Parser.ParserSegmentValue value = segment.ValueList[i];
                i++;
                if (predicate(value)) return value;
            }
            return null;
        }
        #endregion
        #region Setting pro Parser
        /// <summary>
        /// Vrátí Setting pro Parser
        /// </summary>
        /// <returns></returns>
        private static Parser.ParserSetting CreateSetting()
        {
            Parser.ParserSetting setting = new Parser.ParserSetting("Stack", SEGMENT_TEXT);

            Parser.ParserSegmentSetting segment;

            // Text, končí koncem řádku (pak začne další text):
            segment = new Parser.ParserSegmentSetting(SEGMENT_TEXT);
            segment.Blanks = new string[] { " " };
            segment.Delimiters = new string[] { ":" };
            segment.EndWith = new string[] { "\r", "\n" };
            segment.InnerSegmentsNames = new string[] { SEGMENT_PARAMETERS };
            setting.SegmentSettingAdd(segment);

            // Text v závorkách: nevnořuje se, může obsahovat cokoliv, končí závorkou:
            segment = new Parser.ParserSegmentSetting(SEGMENT_PARAMETERS);
            segment.BeginWith = "(";
            segment.EndWith = new string[] { ")" };
            setting.SegmentSettingAdd(segment);

            // Hotovo:
            return setting;
        }
        private const string SEGMENT_TEXT = "Stack";
        private const string SEGMENT_PARAMETERS = "Parameters";
        private const string SEGMENT_SPACE = "Space";
        #endregion
    }
    #endregion
}
